package com.andy.main.testprocessor;

import com.andy.main.router_annotation.Route;
import com.andy.main.router_annotation.model.RouteMeta;
import com.andy.main.utils.Consts;
import com.andy.main.utils.Log;
import com.andy.main.utils.Utils;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

import static javax.lang.model.element.Modifier.PUBLIC;


/**
 * @version V1.0
 * @描述:
 * @Company:
 * @auth: Created by AN .
 * @time: 2020-03-12 19:52
 * @des
 */
//自动注册
@AutoService(Processor.class)
//当前注解处理器可以处理的注解 替代 {@link AbstractProcessor#getSupportedAnnotationTypes()} 函数
@SupportedAnnotationTypes({Consts.ANN_TYPE_ROUTE})
//@SupportedAnnotationTypes({"com.andy.main.router_annotation.Route"})
//指定java版本  替代 {@link AbstractProcessor#getSupportedSourceVersion()} 函数
@SupportedSourceVersion(SourceVersion.RELEASE_7)

/**
 * 处理器接收的参数 替代 {@link AbstractProcessor#getSupportedOptions()} 函数
 */
@SupportedOptions(Consts.ARGUMENTS_NAME)

public class RouteProcessor extends AbstractProcessor {

    Messager messager;

    Log log;
    Elements elementUtils;
    Types typeUtils;
    Filer filerUtils;

    String moduleName;

     // key:组名 value:类名
    private Map<String, String> rootMap = new TreeMap<>();

     // 分组 key:组名 value:对应组的路由信息
    private Map<String, List<RouteMeta>> groupMap = new HashMap<>();

    //init方法相当于构造函数
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        //日志打印需要通过message打印
        messager = processingEnv.getMessager();

        messager.printMessage(Diagnostic.Kind.NOTE, "RouteProcessor=============init");

        log = Log.newLog(processingEnv.getMessager());
        elementUtils = processingEnv.getElementUtils();
        typeUtils = processingEnv.getTypeUtils();

        //文件处理器,(apt生成的编译文件存放地址 app/build/source/apt/debug/...)
        filerUtils = processingEnv.getFiler();

        //模块名 为了方式多模块多组件开发时,生成相同的 xx$$ROOT$$文件
        Map<String, String> options = processingEnv.getOptions();
        if(!Utils.isEmpty(options)){
            moduleName = options.get(Consts.ARGUMENTS_NAME);
        }
        log.i("TEST:ModuleName::"+moduleName);

        if(Utils.isEmpty(moduleName)){
            throw  new RuntimeException("ModuleName is Null");
        }

    }

    /**
     * {@inheritDoc}
     *  流程:
     *  1.判断注解集合是否为空,不为空则开始处理注解了的节点
     *  2.获取所有的Activity和Service分别处理
     *  3.生成编译文件
     * @param set 使用了注解处理器的节点集合(就是用了几个注解,每个注解 就是一个节点),通过set的元素,可以获取节点信息,节点的父节点信息(父节点就是,比如注解在属性里,类名就是父节点)
     * @param roundEnv 环境,就是被注解的节点
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        messager.printMessage(Diagnostic.Kind.NOTE,"RouteProcessor=============process");

        if(!Utils.isEmpty(set)) {
            Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);

            if (!Utils.isEmpty(routeElements)) {
                processRoute(routeElements);
                return true;
            }
        }

//        for (TypeElement typeElement:set) {
//            messager.printMessage(Diagnostic.Kind.NOTE,typeElement.getQualifiedName());
//
//            //获得父节点 MainActivity
//            Element enclosingElement = typeElement.getEnclosingElement();
//            String className = enclosingElement.getSimpleName() + "_Binding";
//        }
        return false;
    }

    /**
     * 处理节点
     * @param routeElements
     */
    private void processRoute(Set<? extends Element> routeElements) {

        //获取 Activity这个类 的节点信息("android.app.Activity")
        TypeElement activity = elementUtils.getTypeElement(Consts.ACTIVITY);

        //暴露的服务
        TypeElement service = elementUtils.getTypeElement(Consts.ISERVICE);

        for (Element element : routeElements) {
            //获取节点元素的类型
            TypeMirror typeMirror=element.asType();
            log.i("Route class:"+typeMirror.toString());
            log.i("Route activity:"+activity.toString());
            log.i("Route service:"+service.toString());

            Route route = element.getAnnotation(Route.class);
            RouteMeta routeMeta=new RouteMeta();
            //判断类型,对Activity和Service处理
            if(typeUtils.isSubtype(typeMirror, activity.asType())){ //isSubtype可以理解为是instance
                routeMeta=new RouteMeta(RouteMeta.Type.ACTIVITY,route,element);
            }else if(typeUtils.isSubtype(typeMirror, service.asType())){
                routeMeta=new RouteMeta(RouteMeta.Type.ISERVICE,route,element);
            }else {
                throw new RuntimeException("Type is not support");
            }
        //检查是否有配置分组,如果没有,从path中截取
            categories(routeMeta);
        }

        //groupMap
        TypeElement iRoutGroup = elementUtils.getTypeElement(Consts.IROUTE_GROUP);
        TypeElement iRoutRoot = elementUtils.getTypeElement(Consts.IROUTE_ROOT);

        //生成$$Group$$ 记录分组表
        generateGroup(iRoutGroup);

        //生成Root类,记录路由
        try {
            generatedRoot(iRoutRoot,iRoutGroup);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    private void generateGroup(TypeElement iRoutGroup) {
        //创建参数类型(Map<String, RouteMeta>)
        ParameterizedTypeName parameterizedTypeName
                = ParameterizedTypeName.get(
                        ClassName.get(Map.class),           //Map
                        ClassName.get(String.class),        //<String,
                        ClassName.get(RouteMeta.class));    //RouteMate>
        //创建参数
        ParameterSpec atlas = ParameterSpec.builder(parameterizedTypeName, "atlas").build();

        //遍历分组,每一个分组,创建一个$$Group$$类
        for (Map.Entry<String,List<RouteMeta>> entry:groupMap.entrySet()             ) {
            MethodSpec.Builder method = MethodSpec.methodBuilder("loadInto")
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .addParameter(atlas);

            String groupName=entry.getKey();
            List<RouteMeta> groupData = entry.getValue();

            for (RouteMeta routeMeta : groupData) {
                //添加函数体
                //atlas.put("/main/test", RouteMeta.build(RouteMeta.Type.ACTIVITY,SecondActivity.class, "/main/test", "main"));
                // $S = String 会添加上""
                // $T = Class 类
                // $L = 字面量
//                String.format()
                method.addStatement("atlas.put($S,$T.build($T.$L,$T.class,$S,$S))",
                        routeMeta.getPath(),                        //"/main/test"
                        ClassName.get(RouteMeta.class),             //RouteMeta
                        ClassName.get(RouteMeta.Type.class),        //RouteMeta.Type
                        routeMeta.getType(),                        //ACTIVITY
                        ClassName.get((TypeElement) routeMeta.getElement()),//SecondActivity
                        routeMeta.getPath(),                        //"/main/test"
                        routeMeta.getGroup());                      //"main"
            }

            //类名
            String groupClassName = Consts.NAME_OF_GROUP + groupName;

            //创建类
            TypeSpec typeSpec = TypeSpec.classBuilder(groupClassName)
                    .addSuperinterface(ClassName.get(iRoutGroup)) //implements
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(method.build())
                    .build();
            //生成java文件
            JavaFile javaFile = JavaFile.builder(Consts.PACKAGE_OF_GENERATE_FILE, typeSpec).build();
            try {
                javaFile.writeTo(filerUtils);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //存放数据(把group信息存放到root集合中)
            rootMap.put(groupName,groupClassName);
        }

    }

    private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) throws IOException {
        //类型 Map<String,Class<? extends IRouteGroup>> routes>
        //Wildcard 通配符
        ParameterizedTypeName routes = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
                )
        );

        //参数 Map<String,Class<? extends IRouteGroup>> routes> routes
        ParameterSpec rootParamSpec = ParameterSpec.builder(routes, "routes")
                .build();
        //函数 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
        MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder
                (Consts.METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(rootParamSpec);

        //函数体
        for (Map.Entry<String, String> entry : rootMap.entrySet()) {
            loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry
                    .getKey(), ClassName.get(Consts.PACKAGE_OF_GENERATE_FILE, entry.getValue
                    ()));
        }
        //生成 $Root$类
        String rootClassName = Consts.NAME_OF_ROOT + moduleName;
        JavaFile.builder(Consts.PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(rootClassName)
                        .addSuperinterface(ClassName.get(iRouteRoot))
                        .addModifiers(PUBLIC)
                        .addMethod(loadIntoMethodOfRootBuilder.build())
                        .build()
        ).build().writeTo(filerUtils);

        log.i("Generated RouteRoot: " + Consts.PACKAGE_OF_GENERATE_FILE + "." + rootClassName);
    }

    /**
     * 检查是否有配置分组,如果没有,从path中截取
     * @param routeMeta
     */
    private void categories(RouteMeta routeMeta) {
        if(routeVerify(routeMeta)){
            log.i("Group:"+routeMeta.getGroup()+";path:"+routeMeta.getPath());
            List<RouteMeta> routeMetas = groupMap.get(routeMeta.getGroup());
            if(Utils.isEmpty(routeMetas)){
                routeMetas=new ArrayList<>();
                routeMetas.add(routeMeta);
                groupMap.put(routeMeta.getGroup(),routeMetas);
            }else{
                routeMetas.add(routeMeta);
            }
        }else {
            log.i("Group is Error "+routeMeta.getPath());
        }
    }

    /*
     * 校验path合法性,并对group进行校验截取赋值
     */
    private boolean routeVerify(RouteMeta routeMeta) {
        String path=routeMeta.getPath();
        String group=routeMeta.getGroup();
        if(Utils.isEmpty(path) || !path.startsWith("/")){
            return false;
        }
        //从path中截取group信息,赋值给routeMate对象的group
        if(Utils.isEmpty(group)){
            String pathGroup=path.substring(1,path.indexOf("/",1));
            if(Utils.isEmpty(pathGroup)){
                return false;
            }
            routeMeta.setGroup(pathGroup);
        }
        return true;
    }


}

